#pragma once

#include <iostream>
#include <filesystem> //c++17
#include<fstream>
#include <pthread.h>
#include <unistd.h>
#include <string>
#include<ctime>
#include<memory>
#include "Mutex.hpp"

// 日志等级，使用枚举enum + class 相当于有个域
enum class LogLevel
{
    DEBUG,
    INFO,
    WARNING,
    ERROR,
    FATAL
};

// 将日志等级（整型）转换为string
std::string Level2String(LogLevel level)
{
    switch (level)
    {
    case LogLevel::DEBUG:
        return "Debug";
    case LogLevel::INFO:
        return "Info";
    case LogLevel::WARNING:
        return "Warning";
    case LogLevel::ERROR:
        return "Error";
    case LogLevel::FATAL:
        return "Fatal";
    default:
        return "Unknow";
    }
}

//获取时间 --> 获取时间戳 --> 从时间戳中获取具体年、月、日、时、分、秒 --> 将获得的时间转换为字符串
// time --> localtime_t --> snprintf
//2025-10-1 15:37:01
std::string GetCurrenttime()
{
    //获取时间戳
    time_t currtime = time(nullptr);//参数为时区，国内的主机已经配置好了
    //从时间戳中获取具体的时间，并将其转换为字符串
    struct tm currtm;
    localtime_r(&currtime , &currtm);

    //将具体的时间转换为字符串
    char timebuffer[64];
    snprintf(timebuffer , sizeof(timebuffer) , "%4d-%02d-%02d %02d:%02d:%02d",
            currtm.tm_year + 1900,
            currtm.tm_mon+1,
            currtm.tm_mday,
            currtm.tm_hour,
            currtm.tm_min,
            currtm.tm_sec);
    return timebuffer;
}

// 刷新策略，利用继承多态
class LogStrategy
{
public:
    virtual ~LogStrategy() = default;                        // 虚函数重写，使用编译器默认生成的
    virtual void SyncLog(const std::string &logmessage) = 0; //=0 的意识是，这个虚函数必须要重写。编译器没有默认生成
};

// 显示器刷新
class ConsoleLogStrategy : public LogStrategy
{
public:
    ~ConsoleLogStrategy()
    {
    }
    void SyncLog(const std::string &logmessage) override // override 这个关键字会让编译器帮我们对重写的虚函数进行检查
    {
        // 显示器文件也是临界资源，需要加锁保护
        {
            LockGuard lockguard(&_lock);
            // 向显示器写入
            std::cout << logmessage << std::endl;
        }
    }

private:
    Mutex _lock;
};

// 文件刷新
// 创建文件夹，将日志写入该文件夹中的文件
const std::string logdefaultdir = "log";
const static std::string logfilename = "test.log";

class FileLogStrategy : public LogStrategy
{
public:
    FileLogStrategy(std::string pathname = logdefaultdir, std::string filename = logdefaultdir)
        : _dir_path_name(logdefaultdir), _filename(filename)
    {
        // 创建目录，使用C++中的filesystem
        {
            LockGuard lockguard(&_mutex);
            //先判断目录是否存在，不存在则创建
            if(std::filesystem::exists(_dir_path_name))
            {
                return;
            }
            //创建目录,create_directories ，创建目录可能会失败，需要捕获异常
            try
            {
                std::filesystem::create_directories(_dir_path_name);
            }
            catch(const std::filesystem::filesystem_error &e)
            {
                std::cerr << e.what() << '\n';
            }
        }
    }
    void SyncLog(const std::string &logmessage) override
    {
        {
            LockGuard lockguard(&_mutex);
            // 将路径拼接
            std::string target = _dir_path_name;
            target += '/';
            target += _filename;

            // 创建文件
            std::ofstream out(target.c_str(), std::ios::app);//append 追加的形式写入文件当中
            //判断打开文件是否成功
            if(!out.is_open())
            {
                return;
            }
            //将日志信息写入文件中
            out << logmessage << "\n";
            out.close();//关闭文件流
        }
    }
    ~FileLogStrategy()
    {
    }

private:
    std::string _dir_path_name;
    std::string _filename;
    Mutex _mutex;
};

//日志内容
class Logger
{
public:
    //两种刷新策略
    Logger()
    {}
    void EnableConsoleLogStragety()
    {
        _stragety = std::make_unique<ConsoleLogStrategy>();
    }
    void EnableFileLogStragety()
    {
        _stragety = std::make_unique<FileLogStrategy>();
    }

    //形成一条完整日志的方式，使用内部类
    //[时间] [日志等级] [进程pid] [文件名] [行号] - 可变参数
    class LogMessage
    {
    public:
        //日志等级、文件名、行号、Logger 对象需要传参获取
        LogMessage(LogLevel level , std::string filename , int line , Logger& logger)
        :_curr_time(GetCurrenttime())
        ,_level(level)
        ,_pid(getpid())
        ,_filename(filename)
        ,_line(line)
        ,_logger(logger)
        {
            //将这些信息按照日志格式拼接放入 _loginfo 中
            //利用stringstream 
            std::stringstream ss;
            ss << '[' <<_curr_time << "] "
            << '[' <<   Level2String(_level) << "] "
            <<"[" << _pid <<"] "
            << "[" << _filename << "] "
            << '[' << _line <<"] "
            << " - ";
            _loginfo = ss.str(); 
        }
        //logger(日志等级，文件名，行号) << 可变参数 ;
        template<typename T>
        LogMessage& operator<<(const T& info)
        {
            //将可变参数追加到 _loginfo 后面
            std::stringstream ss;//任何类型的数据可以通过 stringstream 拼接为字符串
            ss << info;
            _loginfo += ss.str();
            return *this;
        }
        //当LogMessage 临时对象的生命周期结束的时候，将 _loginfo 中的数据进行刷新
        ~LogMessage()
        {
            if(_logger._stragety)//保证刷新策略存在
            {
                //调用子类中的刷新方法
                _logger._stragety->SyncLog(_loginfo);
            }
        }
    private:
        std::string _curr_time;
        LogLevel _level;
        pid_t _pid;
        std::string _filename;
        int _line;

        std::string _loginfo;//存放完整的日志信息，以日志的格式
        Logger &_logger;//刷新策略
    };
    LogMessage operator()(LogLevel level , std::string filename , int line )
    {
        return LogMessage(level , filename,line , *this);
    }
    ~Logger()
    {}

private:
    //刷新策略，使用智能指针，基类指针
    std::unique_ptr<LogStrategy> _stragety;
};


Logger logger;//将Logger对象定义为全局的，这样只要包含了这个头文件就可以直接使用这个Logger
#define LOG(level) logger(level,__FILE__ , __LINE__)
//将设置刷新策略用宏封装一下
#define EnableConsoleLogStragety() logger.EnableConsoleLogStragety();
#define EnableFileLogStragety() logger.EnableFileLogStragety();